﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NesEmulatorX._6052OpCodes;
using NesEmulatorX.Util;

namespace NesEmulatorX
{
	public class CPU6502Base
	{
		#region Properties

		public CPU6502OpCode[] Opcodes
		{
			get { return _opcodes; }
			set { _opcodes = value; }
		}
		private CPU6502OpCode[] _opcodes = new CPU6502OpCode[256];

		public byte A
		{
			get { return _a; }
			set { _a = value; }
		}
		private byte _a = 0;

		public byte X
		{
			get { return _x; }
			set { _x = value; }
		}
		private byte _x = 0;

		public byte Y
		{
			get { return _y; }
			set { _y = value; }
		}
		private byte _y = 0;

		public byte Status
		{
			get { return _status; }
			set { _status = value; }
		}
		private byte _status = 0;

		public byte Stack
		{
			get { return _stack; }
			set { _stack = value; }
		}
		private byte _stack = 0;

		public uint PC
		{
			get { return _pc; }
			set { _pc = value; }
		}
		private uint _pc = 0;

		#endregion

		#region Public Methods

		public void Init()
		{
			this.Reset();

			this.MapOpCodes();
		}

		public void Reset()
		{
			this.Status = 0x20;
		}

		public void MapOpCodes()
		{
			#region 0x00

			this.Opcodes[0x00] = new BRK();
			this.Opcodes[0x01] = new ORA_IndirectX();
			this.Opcodes[0x02] = new NOOP();
			this.Opcodes[0x03] = new NOOP();
			this.Opcodes[0x04] = new NOOP();
			this.Opcodes[0x05] = new ORA_ZeroPage();
			this.Opcodes[0x06] = new ASL_ZeroPage();
			this.Opcodes[0x07] = new NOOP();
			this.Opcodes[0x08] = new PHP();
			this.Opcodes[0x09] = new ORA_Immediate();
			this.Opcodes[0x0A] = new ASL_Accumulator();
			this.Opcodes[0x0B] = new NOOP();
			this.Opcodes[0x0C] = new NOOP();
			this.Opcodes[0x0D] = new ORA_Absolute();
			this.Opcodes[0x0E] = new ASL_Absolute();
			this.Opcodes[0x0F] = new NOOP();

			#endregion // 0x00

			#region 0x10

			this.Opcodes[0x10] = new BPL();
			this.Opcodes[0x11] = new ORA_IndirectY();
			this.Opcodes[0x12] = new NOOP();
			this.Opcodes[0x13] = new NOOP();
			this.Opcodes[0x14] = new NOOP();
			this.Opcodes[0x15] = new ORA_ZeroPageX();
			this.Opcodes[0x16] = new ASL_ZeroPageX();
			this.Opcodes[0x17] = new NOOP();
			this.Opcodes[0x18] = new CLC();
			this.Opcodes[0x19] = new ORA_AbsoluteY();
			this.Opcodes[0x1A] = new NOOP();
			this.Opcodes[0x1B] = new NOOP();
			this.Opcodes[0x1C] = new NOOP();
			this.Opcodes[0x1D] = new ORA_AbsoluteX();
			this.Opcodes[0x1E] = new ASL_AbsoluteX();
			this.Opcodes[0x1F] = new NOOP();

			#endregion // 0x10

			#region 0x20

			this.Opcodes[0x20] = new JSR();
			this.Opcodes[0x21] = new AND_IndirectX();
			this.Opcodes[0x22] = new NOOP();
			this.Opcodes[0x23] = new NOOP();
			this.Opcodes[0x24] = new BIT_ZeroPage();
			this.Opcodes[0x25] = new AND_ZeroPage();
			this.Opcodes[0x26] = new ROL_ZeroPage();
			this.Opcodes[0x27] = new NOOP();
			this.Opcodes[0x28] = new PLP();
			this.Opcodes[0x29] = new AND_Immediate();
			this.Opcodes[0x2A] = new ROL_Accumulator();
			this.Opcodes[0x2B] = new NOOP();
			this.Opcodes[0x2C] = new BIT_Absolute();
			this.Opcodes[0x2D] = new AND_Absolute();
			this.Opcodes[0x2E] = new ROL_Absolute();
			this.Opcodes[0x2F] = new NOOP();

			#endregion // 0x20

			#region 0x30

			this.Opcodes[0x30] = new BMI();
			this.Opcodes[0x31] = new AND_IndirectY();
			this.Opcodes[0x32] = new NOOP();
			this.Opcodes[0x33] = new NOOP();
			this.Opcodes[0x34] = new NOOP();
			this.Opcodes[0x35] = new AND_ZeroPageX();
			this.Opcodes[0x36] = new ROL_ZeroPageX();
			this.Opcodes[0x37] = new NOOP();
			this.Opcodes[0x38] = new SEC();
			this.Opcodes[0x39] = new AND_AbsoluteY();
			this.Opcodes[0x3A] = new NOOP();
			this.Opcodes[0x3B] = new NOOP();
			this.Opcodes[0x3C] = new NOOP();
			this.Opcodes[0x3D] = new AND_AbsoluteX();
			this.Opcodes[0x3E] = new ROL_AbsoluteX();
			this.Opcodes[0x3F] = new NOOP();

			#endregion // 0x30

			#region 0x40

			this.Opcodes[0x40] = new RTI();
			this.Opcodes[0x41] = new EOR_IndirectX();
			this.Opcodes[0x42] = new NOOP();
			this.Opcodes[0x43] = new NOOP();
			this.Opcodes[0x44] = new NOOP();
			this.Opcodes[0x45] = new EOR_ZeroPage();
			this.Opcodes[0x46] = new LSR_ZeroPage();
			this.Opcodes[0x47] = new NOOP();
			this.Opcodes[0x48] = new PHA();
			this.Opcodes[0x49] = new EOR_Immediate();
			this.Opcodes[0x4A] = new LSR_Accumulator();
			this.Opcodes[0x4B] = new NOOP();
			this.Opcodes[0x4C] = new JMP_Absolute();
			this.Opcodes[0x4D] = new EOR_Absolute();
			this.Opcodes[0x4E] = new LSR_Absolute();
			this.Opcodes[0x4F] = new NOOP();

			#endregion // 0x40

			#region 0x50

			this.Opcodes[0x50] = new BVC();
			this.Opcodes[0x51] = new EOR_IndirectY();
			this.Opcodes[0x52] = new NOOP();
			this.Opcodes[0x53] = new NOOP();
			this.Opcodes[0x54] = new NOOP();
			this.Opcodes[0x55] = new EOR_ZeroPageX();
			this.Opcodes[0x56] = new LSR_ZeroPageX();
			this.Opcodes[0x57] = new NOOP();
			this.Opcodes[0x58] = new CLI();
			this.Opcodes[0x59] = new EOR_AbsoluteY();
			this.Opcodes[0x5A] = new NOOP();
			this.Opcodes[0x5B] = new NOOP();
			this.Opcodes[0x5C] = new NOOP();
			this.Opcodes[0x5D] = new EOR_AbsoluteX();
			this.Opcodes[0x5E] = new LSR_AbsoluteX();
			this.Opcodes[0x5F] = new NOOP();

			#endregion // 0x50

			#region 0x60

			this.Opcodes[0x60] = new RTS();
			this.Opcodes[0x61] = new ADC_IndirectX();
			this.Opcodes[0x62] = new NOOP();
			this.Opcodes[0x63] = new NOOP();
			this.Opcodes[0x64] = new NOOP();
			this.Opcodes[0x65] = new ADC_ZeroPage();
			this.Opcodes[0x66] = new ROR_ZeroPage();
			this.Opcodes[0x67] = new NOOP();
			this.Opcodes[0x68] = new PLA();
			this.Opcodes[0x69] = new ADC_Immediate();
			this.Opcodes[0x6A] = new ROR_Accumulator();
			this.Opcodes[0x6B] = new NOOP();
			this.Opcodes[0x6C] = new JMP_Indirect();
			this.Opcodes[0x6D] = new ADC_Absolute();
			this.Opcodes[0x6E] = new ROR_Absolute();
			this.Opcodes[0x6F] = new NOOP();

			#endregion // 0x60

			#region 0x70

			this.Opcodes[0x70] = new BVS();
			this.Opcodes[0x71] = new ADC_IndirectY();
			this.Opcodes[0x72] = new NOOP();
			this.Opcodes[0x73] = new NOOP();
			this.Opcodes[0x74] = new NOOP();
			this.Opcodes[0x75] = new ADC_ZeroPageX();
			this.Opcodes[0x76] = new ROR_ZeroPageX();
			this.Opcodes[0x77] = new NOOP();
			this.Opcodes[0x78] = new SEI();
			this.Opcodes[0x79] = new ADC_AbsoluteY();
			this.Opcodes[0x7A] = new NOOP();
			this.Opcodes[0x7B] = new NOOP();
			this.Opcodes[0x7C] = new NOOP();
			this.Opcodes[0x7D] = new ADC_AbsoluteX();
			this.Opcodes[0x7E] = new ROR_AbsoluteX();
			this.Opcodes[0x7F] = new NOOP();

			#endregion

			#region 0x80

			this.Opcodes[0x80] = new NOOP();
			this.Opcodes[0x81] = new STA_IndirectX();
			this.Opcodes[0x82] = new NOOP();
			this.Opcodes[0x83] = new NOOP();
			this.Opcodes[0x84] = new STY_ZeroPage();
			this.Opcodes[0x85] = new STA_ZeroPage();
			this.Opcodes[0x86] = new STX_ZeroPage();
			this.Opcodes[0x87] = new NOOP();
			this.Opcodes[0x88] = new DEY();
			this.Opcodes[0x89] = new NOOP();
			this.Opcodes[0x8A] = new TXA();
			this.Opcodes[0x8B] = new NOOP();
			this.Opcodes[0x8C] = new STY_Absolute();
			this.Opcodes[0x8D] = new STA_Absolute();
			this.Opcodes[0x8E] = new STX_Absolute();
			this.Opcodes[0x8F] = new NOOP();

			#endregion // 0x80

			#region 0x90

			this.Opcodes[0x90] = new BCC();
			this.Opcodes[0x91] = new STA_IndirectY();
			this.Opcodes[0x92] = new NOOP();
			this.Opcodes[0x93] = new NOOP();
			this.Opcodes[0x94] = new STY_ZeroPageX();
			this.Opcodes[0x95] = new STA_ZeroPageX();
			this.Opcodes[0x96] = new STX_ZeroPageX();
			this.Opcodes[0x97] = new NOOP();
			this.Opcodes[0x98] = new TYA();
			this.Opcodes[0x99] = new STA_AbsoluteY();
			this.Opcodes[0x9A] = new TXS();
			this.Opcodes[0x9B] = new NOOP();
			this.Opcodes[0x9C] = new NOOP();
			this.Opcodes[0x9D] = new STA_AbsoluteX();
			this.Opcodes[0x9E] = new NOOP();
			this.Opcodes[0x9F] = new NOOP();

			#endregion // 0x90

			#region 0xA0

			this.Opcodes[0xA0] = new LDY_Immediate();
			this.Opcodes[0xA1] = new LDA_IndirectX();
			this.Opcodes[0xA2] = new LDX_Immediate();
			this.Opcodes[0xA3] = new NOOP();
			this.Opcodes[0xA4] = new LDY_ZeroPage();
			this.Opcodes[0xA5] = new LDA_ZeroPage();
			this.Opcodes[0xA6] = new LDX_ZeroPage();
			this.Opcodes[0xA7] = new NOOP();
			this.Opcodes[0xA8] = new TAY();
			this.Opcodes[0xA9] = new LDA_Immediate();
			this.Opcodes[0xAA] = new TAX();
			this.Opcodes[0xAB] = new NOOP();
			this.Opcodes[0xAC] = new LDY_Absolute();
			this.Opcodes[0xAD] = new LDA_Absolute();
			this.Opcodes[0xAE] = new LDX_Absolute();
			this.Opcodes[0xAF] = new NOOP();

			#endregion // 0xA0

			#region 0xB0

			this.Opcodes[0xB0] = new BCS();
			this.Opcodes[0xB1] = new LDA_IndirectY();
			this.Opcodes[0xB2] = new NOOP();
			this.Opcodes[0xB3] = new NOOP();
			this.Opcodes[0xB4] = new LDY_ZeroPageX();
			this.Opcodes[0xB5] = new LDA_ZeroPageX();
			this.Opcodes[0xB6] = new LDX_ZeroPageX();
			this.Opcodes[0xB7] = new NOOP();
			this.Opcodes[0xB8] = new CLV();
			this.Opcodes[0xB9] = new LDA_AbsoluteY();
			this.Opcodes[0xBA] = new TSX();
			this.Opcodes[0xBB] = new NOOP();
			this.Opcodes[0xBC] = new LDY_AbsoluteX();
			this.Opcodes[0xBD] = new LDA_AbsoluteX();
			this.Opcodes[0xBE] = new LDX_AbsoluteX();
			this.Opcodes[0xBF] = new NOOP();

			#endregion // 0xB0

			#region 0xC0

			this.Opcodes[0xC0] = new CPY_Immediate();
			this.Opcodes[0xC1] = new CMP_IndirectX();
			this.Opcodes[0xC2] = new NOOP();
			this.Opcodes[0xC3] = new NOOP();
			this.Opcodes[0xC4] = new CPY_ZeroPage();
			this.Opcodes[0xC5] = new CMP_ZeroPage();
			this.Opcodes[0xC6] = new DEC_ZeroPage();
			this.Opcodes[0xC7] = new NOOP();
			this.Opcodes[0xC8] = new INY();
			this.Opcodes[0xC9] = new CMP_Immediate();
			this.Opcodes[0xCA] = new DEX();
			this.Opcodes[0xCB] = new NOOP();
			this.Opcodes[0xCC] = new CPY_Absolute();
			this.Opcodes[0xCD] = new CMP_Absolute();
			this.Opcodes[0xCE] = new DEC_Absolute();
			this.Opcodes[0xCF] = new NOOP();

			#endregion // 0xC0

			#region 0xD0

			this.Opcodes[0xD0] = new BNE();
			this.Opcodes[0xD1] = new CMP_IndirectY();
			this.Opcodes[0xD2] = new NOOP();
			this.Opcodes[0xD3] = new NOOP();
			this.Opcodes[0xD4] = new NOOP();
			this.Opcodes[0xD5] = new CMP_ZeroPageX();
			this.Opcodes[0xD6] = new DEC_ZeroPageX();
			this.Opcodes[0xD7] = new NOOP();
			this.Opcodes[0xD8] = new CLD();
			this.Opcodes[0xD9] = new CMP_AbsoluteY();
			this.Opcodes[0xDA] = new NOOP();
			this.Opcodes[0xDB] = new NOOP();
			this.Opcodes[0xDC] = new NOOP();
			this.Opcodes[0xDD] = new CMP_AbsoluteX();
			this.Opcodes[0xDE] = new DEC_AbsoluteX();
			this.Opcodes[0xDF] = new NOOP();

			#endregion // 0xD0

			#region 0xE0

			this.Opcodes[0xE0] = new CPX_Immediate();
			this.Opcodes[0xE1] = new SBC_IndirectX();
			this.Opcodes[0xE2] = new NOOP();
			this.Opcodes[0xE3] = new NOOP();
			this.Opcodes[0xE4] = new CPX_ZeroPage();
			this.Opcodes[0xE5] = new SBC_ZeroPage();
			this.Opcodes[0xE6] = new INC_ZeroPage();
			this.Opcodes[0xE7] = new NOOP();
			this.Opcodes[0xE8] = new INX();
			this.Opcodes[0xE9] = new SBC_Immediate();
			this.Opcodes[0xEA] = new NOP();
			this.Opcodes[0xEB] = new NOOP();
			this.Opcodes[0xEC] = new CPX_Absolute();
			this.Opcodes[0xED] = new SBC_Absolute();
			this.Opcodes[0xEE] = new INC_Absolute();
			this.Opcodes[0xEF] = new NOOP();

			#endregion // 0xE0

			#region 0xF0

			this.Opcodes[0xF0] = new BEQ();
			this.Opcodes[0xF1] = new SBC_IndirectY();
			this.Opcodes[0xF2] = new NOOP();
			this.Opcodes[0xF3] = new NOOP();
			this.Opcodes[0xF4] = new NOOP();
			this.Opcodes[0xF5] = new SBC_ZeroPageX();
			this.Opcodes[0xF6] = new INC_ZeroPageX();
			this.Opcodes[0xF7] = new NOOP();
			this.Opcodes[0xF8] = new SED();
			this.Opcodes[0xF9] = new SBC_AbsoluteY();
			this.Opcodes[0xFA] = new NOOP();
			this.Opcodes[0xFB] = new NOOP();
			this.Opcodes[0xFC] = new NOOP();
			this.Opcodes[0xFD] = new SBC_AbsoluteX();
			this.Opcodes[0xFE] = new INC_AbsoluteX();
			this.Opcodes[0xFF] = new NOOP();

			#endregion // 0xF0
		}

		public void ToggleCarryFlag(bool b)
		{
			if (b)
				this.Status = Binary.SetBit8(this.Status);
			else
				this.Status = Binary.UnsetBit8(this.Status);
		}

		public bool GetCarryFlag()
		{
			return Binary.Bit8(this.Status) > 0;
		}

		public void SetCarryFlag(byte b, bool subtract = false)
		{
			if(subtract)
				this.ToggleCarryFlag(Binary.Bit1(b) == 0);
			else
				this.ToggleCarryFlag(Binary.Bit1(b) > 0);
		}

		public void SetZeroFlag(byte b)
		{
			if (b == 0)
				this.Status = Binary.SetBit7(this.Status);
			else
				this.Status = Binary.UnsetBit7(this.Status);
		}

		public bool GetZeroFlag()
		{
			return Binary.Bit7(this.Status) > 0;
		}

		public void SetInteruptFlag(bool b)
		{
			if (b)
				this.Status = Binary.SetBit6(this.Status);
			else
				this.Status = Binary.UnsetBit6(this.Status);
		}

		public bool GetInteruptFlag()
		{
			return Binary.Bit6(this.Status) > 0;
		}

		public void SetDecimalFlag(bool b)
		{
			if (b)
				this.Status = Binary.SetBit5(this.Status);
			else
				this.Status = Binary.UnsetBit5(this.Status);
		}

		public bool GetDecimalFlag()
		{
			return Binary.Bit5(this.Status) > 0;
		}

		public void SetBRKFlag(bool b)
		{
			if (b)
				this.Status = Binary.SetBit4(this.Status);
			else
				this.Status = Binary.UnsetBit4(this.Status);
		}

		public bool GetBRKFlag()
		{
			return Binary.Bit4(this.Status) > 0;
		}

		public bool GetBit3Flag()
		{
			return Binary.Bit3(this.Status) > 0;
		}

		public void SetOverflowFlag(int i)
		{
			if (i > 0xFF)
				this.Status = Binary.SetBit2(this.Status);
			else
				this.Status = Binary.UnsetBit2(this.Status);
		}

		public bool GetOverflowFlag()
		{
			return Binary.Bit2(this.Status) > 0;
		}

		public void SetSignFlag(int i)
		{
			if (i < 0x0)
				this.Status = Binary.SetBit1(this.Status);
			else
				this.Status = Binary.UnsetBit1(this.Status);
		}

		public bool GetSignFlag()
		{
			return Binary.Bit1(this.Status) > 0;
		}

		#endregion

		#region Private Methods

		#endregion
	}
}
